using AutoMapper;
using Enter.Admin.Configs;
using Enter.Admin.Entities;
using Enter.Admin.Filters;
using Enter.Admin.Tasks;
using Enter.Admin.Utils;
using FluentScheduler;
using FluentValidation.AspNetCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Serialization;
using Serilog;
using System;
using System.Reflection;

namespace Enter.Admin
{
    /*
     实际操作，按住Ctrl键不放，先按K键，再按F键，其他操作是类似的。

格式化全部代码       Ctrl+A+K+F

格式化选中的代码     Ctrl+K+F

注释代码   Ctrl+K+C（comment）

反注释代码  Ctrl+K+U
     */
    public class Startup
    {
        public Startup(IConfiguration configuration, IWebHostEnvironment env)
        {
            Configuration = configuration;
           
            Fsql = new FreeSql.FreeSqlBuilder()
                .UseConnectionString(FreeSql.DataType.MySql, GetConnectionString())
                //.UseAutoSyncStructure(true)
                //.UseMonitorCommand(cmd => Console.WriteLine(cmd.CommandText))
                .UseNoneCommandParameter(true)
                .Build();
            //Fsql.Aop.TraceBefore += (_, e) => Trace.WriteLine($"----TraceBefore---{e.Identifier} {e.Operation}");
            //Fsql.Aop.TraceAfter += (_, e) => Trace.WriteLine($"----TraceAfter---{e.Identifier} {e.Operation} {e.Remark} {e.Exception?.Message} {e.ElapsedMilliseconds}ms\r\n");
            //BaseEntity.Initialization(Fsql, () => _asyncUow.Value);
            
            // 初始化;
            Controllers.BaseController.Init(Fsql, Configuration);
        }

        private string GetConnectionString()
        {
            var conStr = Environment.GetEnvironmentVariable("ConnectionString");
            return string.IsNullOrWhiteSpace(conStr) ? Configuration.GetConnectionString("Default") : conStr;            
        }

        public IConfiguration Configuration { get; }
        public static IFreeSql Fsql { get; private set; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddOptions();
            //services.Configure<SidebarMenu>(Configuration.GetSection("sidebarMenu"));


            // https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/app-state?view=aspnetcore-5.0
            //services.AddDistributedMemoryCache();
            // dotnet tool install --global Pomelo.Extensions.Caching.MySqlConfig.Tools --version 2.1.0
            /*
             * https://docs.microsoft.com/zh-cn/aspnet/core/performance/caching/distributed?view=aspnetcore-5.0#distributed-sql-server-cache
             * https://github.com/PomeloFoundation/Caching-MySQL
             * https://www.cnblogs.com/qiyebao/p/12696613.html
             --sql server
                dotnet tool install --global dotnet-sql-cache
                dotnet sql-cache create "Server=192.168.0.44;User Id=sa;Password=1234;Database=Program_Test;" dbo AspNetCoreCache

                --mysql
                dotnet tool install --global Pomelo.Extensions.Caching.MySqlConfig.Tools --version 2.1.0
                dotnet mysql-cache create "server=49.235.74.85;port=3306;uid=root;pwd=123456;database=yiyandb;Charset=utf8;Max pool size=100;Allow User Variables=True;" yiyandb DistributedCache
             */
            // server=49.235.74.85;port=3306;uid=root;pwd=123456;database=yiyandb;Charset=utf8;Max pool size=100;Allow User Variables=True;
            services.AddDistributedMySqlCache(options =>
            {
                options.ConnectionString = GetConnectionString();
                options.SchemaName = "yiyandb";
                options.TableName = "DistributedCache";
            });
            services.AddSession(options =>
            {
                // 设置 Session 过期时间12小时
                options.IdleTimeout = TimeSpan.FromHours(12);
                options.Cookie.HttpOnly = true;
                options.Cookie.IsEssential = true;
            });
            services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
            // https://www.tnblog.net/15985459135/article/details/3521
            // https://docs.automapper.org/en/stable/Dependency-injection.html
            services
            // 手动注册;
            //.AddAutoMapper(config =>
            //{
            //    config.CreateMap<Archive, ArchiveModel>();
            //    config.CreateMap<ArchiveModel,Archive> ();
            //    // _mapper.Map<ArchiveModel>(new Archive() { Id=10, CreateTime =DateTime.Now});
            //      //cfg.ForAllMaps((a, b) => b.ForAllMembers(opt => opt.Condition((src, dest, sourceMember) => sourceMember != null)));
            //})
            // 自动发现(继承Profile);
            .AddAutoMapper(config => {
                config.ForAllMaps((a, b) => b.ForAllMembers(opt => opt.DoNotAllowNull()));//opt => opt.NullSubstitute("默认值")
            },Assembly.GetAssembly(typeof(Startup)));

            // form提交字符串为null https://www.cnblogs.com/zhoushangwu/p/13867606.html
            // 1. 字段上加入[DisplayFormat(ConvertEmptyStringToNull = false)]
            services.AddControllersWithViews(options => {
                // form提交字符串为null
                options.ModelMetadataDetailsProviders.Add(new CustomMetadataProvider());
                // https://www.c-sharpcorner.com/article/custom-model-binding-in-asp-net-core-mvc/
                //options.ModelBinderProviders.Add();
            })
            .AddNewtonsoftJson(options =>
            {
                // 大小写;
                options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
                options.SerializerSettings.ContractResolver = new DefaultContractResolver();
                options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";
                /*
                 public class student
  {
    public string Name { get; set; }
    public int Age { get; set; }
    [JsonConverter(typeof(DateFormat))]
    public DateTime BirthDay { get; set; }
    [JsonConverter(typeof(DateTimeFormat))]
    public DateTime CreateTime { get; set; }
  }
  public class DateFormat:IsoDateTimeConverter
  {
    public DateFormat()
    {
      base.DateTimeFormat = "yyyy-MM-dd";
    }
  }
  public class DateTimeFormat:IsoDateTimeConverter
  {
    public DateTimeFormat()
    {
      base.DateTimeFormat = "yyyy-MM-dd HH:mm:ss";
    }
  }
                 */
            });//.AddJsonOptions(options => { options.JsonSerializerOptions.PropertyNamingPolicy=null; });
               //services.AddSingleton<IObjectModelValidator, NullObjectModelValidator>();
            services.AddTransient<IValidatorInterceptor, DefaultFluentValidatorInterceptor>();
            services.AddMvc(config =>
            {
                config.MaxModelValidationErrors = 5;
                config.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true;
                //config.ModelValidatorProviders.Clear();
                //config.AllowEmptyInputInBodyModelBinding = true;
                config.Filters.Add(typeof(ExceptionFilter));
                config.Filters.Add<ValidatorFilter>();                
                //config.Filters.Add<ActionFilter>();
            })
            .AddFluentValidation(conf=> {
                conf.RunDefaultMvcValidationAfterFluentValidationExecutes = false;
                // 自动发现;
                conf.RegisterValidatorsFromAssembly(Assembly.GetAssembly(typeof(Startup)));
                //conf.RegisterValidatorsFromAssemblyContaining<IValidator>();
                //conf.ImplicitlyValidateChildProperties = true; // 请注意，如果启用此行为，则不应将其 SetValidator 用于子属性，否则验证程序将执行两次。
                // 新增一个规则，修改一个规则 https://docs.fluentvalidation.net/en/latest/rulesets.html
            });
            // ApiBehaviorOptions  验证与ApiControllerAttribute
            //services.Configure<ApiBehaviorOptions>(ops =>
            //{
            //    //ops.SuppressModelStateInvalidFilter = true;// 使用自定义模型验证 使用这个就要自定义拦截器IActionFilter
            //    ops.InvalidModelStateResponseFactory = (context) =>
            //    {
            //        var errors = context.ModelState
            //          .Values
            //          .SelectMany(x => x.Errors
            //                .Select(p => p.ErrorMessage))
            //          .ToList();
            //        var result = new ResponseResult<List<string>>
            //        {
            //            Success = false,
            //            Data = errors,
            //            Message = string.Join(",", errors.Select(e => string.Format("{0}", e)).ToList()),
            //        };
            //       // return new JsonResult(result);
            //        return new BadRequestObjectResult(result);
            //    };
            //});
            // https://docs.fluentvalidation.net/en/latest/aspnet.html
            // 手动注册
            //services.AddTransient<IValidator<ArchiveModel>, ArchiveModelValidator>();

            services.AddSingleton<IFreeSql>(Fsql);

            // https://blog.csdn.net/szqhjohn/article/details/102833504
            //services.AddAuthentication("CookieScheme")
            //.AddCookie("CookieScheme", option =>
            //{
            //    // 登录路径：这是当用户试图访问资源但未经过身份验证时，程序将会将请求重定向到这个相对路径。
            //    option.LoginPath = new PathString("/account/login");
            //    // 禁止访问路径：当用户试图访问资源时，但未通过该资源的任何授权策略，请求将被重定向到这个相对路径
            //    option.AccessDeniedPath = new PathString("/account/denied");
            //});
            /*
              //用Claim来构造一个ClaimsIdentity，然后调用 SignInAsync 方法。
          var claims = new List<Claim>();
          claims.Add(new Claim(ClaimTypes.Name, userName));
          var claimsIdentity = new ClaimsIdentity(claims, "Cookies");
          //登录
          await HttpContext.SignInAsync("Cookies", new ClaimsPrincipal(claimsIdentity));
            //退出
      await HttpContext.SignOutAsync("Cookies");

            app.UseAuthentication();// 认证
             */

            // https://docs.hangfire.io/en/latest/getting-started/aspnet-core-applications.html
            // https://github.com/arnoldasgudas/Hangfire.MySqlStorage

            // services.AddHangfire(x => x.UseStorage(new MySqlStorage(
            //         GetConnectionString(),
            //         new MySqlStorageOptions
            //         {
            //             TransactionIsolationLevel = IsolationLevel.ReadCommitted,
            //             QueuePollInterval = TimeSpan.FromSeconds(15),
            //             JobExpirationCheckInterval = TimeSpan.FromHours(1),
            //             CountersAggregateInterval = TimeSpan.FromMinutes(5),
            //             PrepareSchemaIfNecessary = true,
            //             DashboardJobListLimit = 50000,
            //             TransactionTimeout = TimeSpan.FromMinutes(1),
            //             TablesPrefix = "Hangfire_"
            //         })
            //     ));
            // services.AddHangfireServer();
            // 注册配置;
            services.Configure<MailModel>(Configuration.GetSection("Mail"));
            services.Configure<AliyunOss>(Configuration.GetSection("Aliyun:OSS"));
            // 注册工具;
            services.AddScoped<Mail>();
            services.AddScoped<Upload>();
            // 最后注册任务;
            //services.AddHostedService<TimedHostedService>();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger<FluentSchedulerRegistry> logger)
        {
            // 中间件 https://www.toutiao.com/i6899603828812022279/
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
            }

            app.UseSerilogRequestLogging( options =>
            {
                //// Customize the message template
                //options.MessageTemplate = "Handled {RequestPath}";

                //// Emit debug-level events instead of the defaults
                //options.GetLevel = (httpContext, elapsed, ex) => LogEventLevel.Debug;

                //// Attach additional properties to the request completion event
                //options.EnrichDiagnosticContext = (diagnosticContext, httpContext) =>
                //{
                //    diagnosticContext.Set("RequestHost", httpContext.Request.Host.Value);
                //    diagnosticContext.Set("RequestScheme", httpContext.Request.Scheme);
                //};
            });

            app.UseStaticFiles();

            app.UseRouting();

            //app.UseAuthentication();// 认证

            app.UseAuthorization(); // 授权

            /*
             Authentication（认证） [Authorize]
            明确是你谁，确认是不是合法用户。常用的认证方式有用户名密码认证。

            Authorization（授权）
            明确你是否有某个权限。当用户需要使用某个功能的时候，系统需要校验用户是否需要这个功能的权限。

            在UseAuthentication之后再次UseAuthorization
             */

            app.UseSession();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Case}/{action=Index1}/{id?}");
                // https://docs.hangfire.io/en/latest/configuration/using-dashboard.html#configuring-authorization
                // endpoints.MapHangfireDashboard(new DashboardOptions() { 
                //     //DisplayStorageConnectionString = false,
                //     Authorization = new[] { new HangfireDashboardAuthorizationFilter() }
                // });
            });

            // 开始任务;
            JobManager.Initialize(new FluentSchedulerRegistry(logger, Fsql, Configuration));

            // https://github.com/HangfireIO/Hangfire
            // https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-5.0&tabs=visual-studio
            // app.UseHangfireDashboard();
            // 任务执行一次
            //BackgroundJob.Enqueue(() => Console.WriteLine("Hello, world!"));
            //任务每分钟执行一次 Hangfire 的 Cron表达式https://www.yht7.com/news/87539
            //RecurringJob.AddOrUpdate(() => Console.WriteLine($"ASP.NET Core LineZero"), Cron.Minutely());
            //任务延时两分钟执行
            //BackgroundJob.Schedule(() => Console.WriteLine($"ASP.NET Core await LineZero{DateTime.Now}"), TimeSpan.FromMinutes(2));
        }
    }
}
